/*
 * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
 * Copyright (C) 2023, 2024  Mio
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
module pd.configuration;

import pd.converter;

private enum ProjectQualifier = null;
private enum ProjectOrg = "YumeNeru Software";
private enum ProjectName = "pixiv_down";
private enum PHPSessionIDFileName = ".phpsessid";

/// pixiv_down configuration.
///
/// Use `configuration.loadConfig` to load the correct values.
struct Config
{
   /// The directory where content is downloaded to.
   string outputDirectory;
   /// The PHPSESSID of the current account.
   string sessionid;
   /// Cross Site Request Forgery Token (unused)
   string csrfToken;
   /// Two character locale used for pixiv requests
   string locale;
   /// Configuration for the `bookmarked` command
   BookmarkedConfig bookmarked;

   /* TODO: I don't like this setup. That the 'converter' is
       contained in a configuration struct isn't good, it should
       be in a context, and that context can have a configuration.
    */

   /// Name of converter that is being used.
   string converterName;

   // Converter used for converting images.
   ConverterManager manager;
}

private struct BookmarkedConfig
{
   /// Whether invalid works should automatically be removed
   /// from the account's bookmarks.
   bool alwaysRemoveInvalid;
}

public Config loadConfig(string path)
{
   import mlib.configparser: ConfigParser;
   import mlib.directories: getUserDirectories;
   import std.exception: ErrnoException;
   import std.experimental.logger: errorf, tracef;

   const userDirectories = getUserDirectories();

   tracef("Loading configuration file at %s", path);
   try {
      auto parser = new ConfigParser();
      parser.read(path);

      const outputDirectory = parser.get("output", "directory",
         parser.get("output", "base_folder", userDirectories.pictureDir));
      const alwaysRemoveInvalid = parser.getBool("bookmarked", "always_remove_invalid", false);
      const converterName = parser.get("converter", "name", null);

      return Config(outputDirectory, fetchSessionID(), "", "",
         BookmarkedConfig(alwaysRemoveInvalid), converterName);
   } catch(ErrnoException e) {
      errorf("Failed to load configuration file: %s", e.msg);
      // Fallback to default values
      return Config(userDirectories.pictureDir, fetchSessionID());
   }
}

/// Load the configuration for pixiv_down.
public Config loadConfig()
{
   import mlib.directories: getProjectDirectories;
   import std.file: exists;
   import std.path: buildPath;

   const projectDirs = getProjectDirectories(ProjectQualifier, ProjectOrg, ProjectName);
   const newConfigPath = buildPath(projectDirs.configDir, "pixiv_down.conf");
   const oldConfigPath = buildPath(projectDirs.configDir, "settings.conf");
   /* I'd prefer to use the new config path over the old one when possible,
    * but if that's the only one present then use it. */
   const path = exists(newConfigPath) ? newConfigPath : (exists(oldConfigPath) ? oldConfigPath : newConfigPath);

   return loadConfig(path);
}

/// Prompt to reset the PHPSESSID.
///
/// This will store the value in the appropriate location so it
/// can be read in the future.
public void resetSessionID() @safe
{
   import mlib.directories: getProjectDirectories;
   import std.file: exists, mkdirRecurse;
   import std.path: buildPath;

   const projectDirs = getProjectDirectories(ProjectQualifier, ProjectOrg, ProjectName);
   const id = promptForSessionID();

   if (false == exists(projectDirs.dataDir)) {
      mkdirRecurse(projectDirs.dataDir);
   }

   storeSessionID(id, buildPath(projectDirs.dataDir, PHPSessionIDFileName));
}

private string promptForSessionID() @trusted
{
   import std.stdio : readln, stderr, write;
   import std.string : empty, strip;

   import core.stdc.stdlib : exit;

   stderr.writeln("========== NOTICE ==========");
   stderr.writeln("No PHPSESSID found.\n");
   stderr.writeln("Please see the below webpage for instructions to find your PHPSESSID");
   stderr.writeln("https://codeberg.org/supercell/pixiv_down/src/branch/trunk/HowTo-PHPSESSID.txt");
   stderr.writeln("============================");

   write("\nPlease enter your PHPSESSID (leave blank to exit): ");

   const sessionid = readln.strip;

   if (empty(sessionid))
   {
      exit(0);
   }

   return sessionid;
}

pragma(inline, true)
private void storeSessionID(const(char)[] sessionid, const(char)[] path) @safe
{
   import std.stdio : File;

   File(path, "w+").writeln(sessionid);
}

private string fetchSessionID() /* @safe */
{
   import std.file : exists, mkdirRecurse;
   import std.path : buildPath;
   import std.process : environment;
   import std.stdio : File;
   import std.string : empty, strip;
   import mlib.directories : getProjectDirectories;

   // Environment variable takes precedence over stored value
   const environmentValue = environment.get("PIXIV_DOWN_SESSID");
   if (environmentValue !is null) {
      return environmentValue;
   }

   const projectDirs = getProjectDirectories(ProjectQualifier, ProjectOrg, ProjectName);
   const sessidPath = buildPath(projectDirs.dataDir, PHPSessionIDFileName);

   if (false == exists(sessidPath)) {
      mkdirRecurse(projectDirs.dataDir);
      const newID = promptForSessionID();
      storeSessionID(newID, sessidPath);
      return newID;
   }

   const sessionid = File(sessidPath).readln.strip;

   if (empty(sessionid)) {
      const newID = promptForSessionID();
      storeSessionID(newID, sessidPath);
      return newID;
   }

   return sessionid;
}
